package model.data.tableModel.sale;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import enums.Status;
import view.GUI.mainView.KindVBox.BoxType;
import model.data.interfaces.IFinishable;
import model.data.tableModel.TableControl;
import model.handler.databaseHandling.QueryFactory;

/**
 * Representing the table Product.
 * <p>
 * Contains all information about a product that is sold at the store.
 */
public final class Product implements IFinishable, Comparable<Product> {

	private final static Logger debug = LogManager.getLogger("DEBUG");

	private final int PID;
	private final double cost;
	private int availableBegin;
	private int availableEnd;
	private double salePrice;
	private Product deposit;
	private String name;
	private BoxType category;
	private int multiplicator;
	private int stock;
	private int maxStock;
	private Product replacement;
	private int trayCount;
	private int groupID;
	private String stockTable;
	private String trayTable;

	private Status status;

	private Product(final int pID, final String name, final double cost, final int availableBegin, final int availableEnd, final double salePrice,
			final Product deposit, Status status, final int stock, final int multiplicator, final BoxType category, Product hasReplacement,
			int maxStock, int trayCount, int groupID) {
		assert pID >= 0;
		assert cost >= 0;
		assert availableBegin >= 0;
		assert availableEnd >= 0;
		assert salePrice >= 0;
		assert stock >= 0;
		assert multiplicator >= 0;
		assert maxStock >= 0;
		assert name != null && !"".equals(name);

		this.trayCount = trayCount;
		this.PID = pID;
		this.cost = cost;
		this.availableBegin = availableBegin;
		this.availableEnd = availableEnd;
		this.salePrice = salePrice;
		this.deposit = deposit;
		this.status = status;
		this.stock = stock;
		this.name = name;
		this.multiplicator = multiplicator;
		this.category = category;
		this.replacement = hasReplacement;
		this.maxStock = maxStock;
		this.groupID = groupID;
		this.setStockTable(String.valueOf(stock));
		this.setTrayTable(String.valueOf(trayCount));
	}

	@Override
	public String toString() {
		return name;
	}

	public static Product createExistingProduct(int pID, String name, double cost, int availableBegin, int availableEnd, double salePrice,
			Product deposit, Status status, int stock, int multiplicator, BoxType category, Product hasReplacement, int maxStock, int trayCount,
			int groupID) {
		Product p = new Product(pID, name, cost, availableBegin, availableEnd, salePrice, deposit, status, stock, multiplicator, category,
				hasReplacement, maxStock, trayCount, groupID);

		debug.debug("Existing Product " + name + " created ID: " + pID);
		return p;
	}

	public static Product createNewProduct(double cost, String name, int availableBegin, int availableEnd, double salePrice, Product deposit,
			Status status, int stock, int multiplicator, BoxType category, Product hasReplacement, int maxStock, int trayCount, int groupID) {
		int id = TableControl.getCentralCounter();
		Product p = new Product(id, name, cost, availableBegin, availableEnd, salePrice, deposit, status, stock,
				multiplicator, category, hasReplacement, maxStock, trayCount, groupID);
		debug.debug("New Product " + name + " created ID: " + id);
		return p;
	}

	@Override
	public boolean writeToDB() {
		return QueryFactory.writeProductToDB(false, PID, name, cost, availableBegin, availableEnd, salePrice, deposit, status, category, stock,
				replacement, multiplicator, maxStock, trayCount, groupID);
	}

	@Override
	public boolean writeUpdateToDB() {
		// if(replacement != null){
		// replacement.writeUpdateToDB();
		// }

		return QueryFactory.writeProductToDB(true, PID, name, cost, availableBegin, availableEnd, salePrice, deposit, status, category, stock,
				replacement, multiplicator, maxStock, trayCount, groupID);
	}

	public boolean writeReplacementUpdateToDB() {
		if (replacement != null) {
			return replacement.writeUpdateToDB();
		}
		return true;
	}

	@Override
	public int hashCode() {

		final int prime = 31;
		int result = 1;
		result = prime * result + PID;
		result = prime * result + ((category == null) ? 0 : category.hashCode());
		long temp;
		temp = Double.doubleToLongBits(cost);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		result = prime * result + ((deposit == null) ? 0 : deposit.hashCode());
		result = prime * result + multiplicator;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {

		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Product other = (Product) obj;
		if (PID != other.PID)
			return false;
		if (category != other.category)
			return false;
		if (Double.doubleToLongBits(cost) != Double.doubleToLongBits(other.cost))
			return false;
		if (deposit == null) {
			if (other.deposit != null)
				return false;
		} else if (!deposit.equals(other.deposit))
			return false;
		if (multiplicator != other.multiplicator)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	/**
	 * Reduces the stock of a product.
	 */
	public void reduceStock(int amount) {

		this.stock = this.stock - amount;
	}

	public void setStock(int isStock) {
		this.stock = isStock;
		this.writeUpdateToDB();
	}

	@Override
	public int compareTo(Product o) {
		return this.name.compareToIgnoreCase(o.getName());
	}

	public Product copy(double cost, double salePrice) {
		return Product.createNewProduct(cost, this.name, this.availableBegin, this.availableEnd, salePrice, this.deposit, this.status, this.stock,
				this.multiplicator, this.category, this.replacement, this.maxStock, this.trayCount, this.groupID);
	}

	/**
	 * Changes the purchase conditions of a product.
	 * <p>
	 * Case cost AND salePrice change: 
	 * 1. Create new product with old cost and
	 * new salePrice 
	 * 2. Set new salePrice for all replacements of the newly
	 * created Product 
	 * 3. Set original PRoduct inactive and create a new Product
	 * with the new salePrice and the new cost 
	 * 4. Set the completeNewProduct as
	 * last replacement of the Product
	 * 
	 * @param cost
	 * @param salePrice
	 * @return null if no Product should be added to the instantiated products |
	 *         Product to add to the instantiated products
	 */
	public Product changePurchaseConditions(double cost, double salePrice) {

		if (this.getCost() != cost && this.getSalePrice() != salePrice) {

			Product newProductOldCost = this.copy(this.getCost(), salePrice);

			newProductOldCost.setAllNewSalePrice(salePrice);

			Product completeNewProduct = this.copy(cost, salePrice);
			this.status = Status.INACTIVE;

			completeNewProduct.setReplacement(null);
			newProductOldCost.setLastReplacement(completeNewProduct);
			completeNewProduct.setStock(0);
			completeNewProduct.setTrayCount(0);
			completeNewProduct.setStatus(Status.WAIT);
			completeNewProduct.writeToDB();
			newProductOldCost.writeToDB();
			newProductOldCost.writeUpdateToDB();
			newProductOldCost.writeReplacementUpdateToDB();
			this.writeUpdateToDB();
			return newProductOldCost;

		} else if (this.getCost() != cost) {
			Product newCostProduct = this.getLatestProduct().copy(cost, salePrice);
			newCostProduct.setStock(0);
			newCostProduct.setTrayCount(0);
			newCostProduct.setStatus(Status.WAIT);
			this.setLastReplacement(newCostProduct);
			newCostProduct.writeToDB();
			this.writeUpdateToDB();
			this.writeReplacementUpdateToDB();
			return null;

		} else if (this.getSalePrice() != salePrice) {
			Product newSalePriceProduct = this.copy(cost, salePrice);
			newSalePriceProduct.setAllNewSalePrice(salePrice);
			this.status = Status.INACTIVE;
			newSalePriceProduct.writeToDB();
			this.writeUpdateToDB();
			this.writeReplacementUpdateToDB();
			return newSalePriceProduct;
		}
		return null;
	}
	
	/* GETTER AND SETTER BELOW */
	

	public void setTrayCount(int trayCount) {
		this.trayCount = trayCount;
	}

	private void setAllNewSalePrice(double salePrice) {
		this.salePrice = salePrice;
		if (replacement != null) {
			replacement.setAllNewSalePrice(salePrice);
		}
	}

	/**
	 * @return The most updated version of the product. That is the last
	 *         replacement in this row, it contains the most updated
	 *         information.
	 */
	public Product getLatestProduct() {
		if (replacement == null) {
			return this;
		} else {
			return replacement.getLatestProduct();
		}
	}

	public int getTrayCount() {
		return this.trayCount;
	}

	public int getAllTrays() {
		if (replacement != null) {
			return trayCount + replacement.getAllTrays();
		} else {
			return trayCount;
		}
	}

	/**
	 * Divides the trayCount that is noted by an inventory to the product and
	 * its replacements
	 * 
	 * @param trayCount number of trays that were counted at the inventory
	 */
	public void setAllTrayCount(int trayCount) {
		int multiplicator = this.multiplicator;
		if (deposit != null) {
			multiplicator = deposit.getMultiplicator();
		}
		if (replacement != null) {
			this.trayCount = (int) (Math.ceil(this.stock / (multiplicator * trayCount)));
			replacement.setAllTrayCount(trayCount - this.trayCount);
		} else {
			this.trayCount = trayCount;
		}
	}

	public int getGroupID() {
		return groupID;
	}

	public String getStockTable() {
		return stockTable;
	}

	public void setStockTable(String stockTable) {
		this.stockTable = stockTable;
	}

	public Status getStatus() {
		return status;
	}

	public void setDeposit(Product product) {
		this.deposit = product;
	}

	public String getTrayTable() {
		return trayTable;
	}

	public void setTrayTable(String trayTable) {
		this.trayTable = trayTable;
	}	

	public void setSalePrice(Double salePrice) {
		this.salePrice = salePrice;
	}

	public void setLastReplacement(Product prod) {
		if (this.replacement != null) {
			replacement.setLastReplacement(prod);
		} else {
			this.replacement = prod;
		}
	}

	public void setMaxStock(int maxStock) {
		this.maxStock = maxStock;
	}

	public void setName(String name) {
		this.name = name;
	}

	public void setReplacement(Product replacement) {
		this.replacement = replacement;
	}

	public void setCategory(BoxType category) {
		this.category = category;
	}

	public Product getReplacement() {
		return replacement;
	}

	public int getMaxStock() {
		return maxStock;
	}	

	/**
	 * @return The stock of this instance and all replacement Products
	 */
	public int getAvailability() {
		int stock = this.stock;
		if (replacement != null) {
			stock += replacement.getAvailability();
		}
		return stock;
	}

	@Override
	public void setStatus(Status status) {
		this.status = status;
	}

	public int getStock() {

		return stock;
	}

	public BoxType getCategory() {
		return category;
	}

	public void addStock(int value) {
		this.stock += value;
	}

	public double getCost() {
		return cost;
	}	

	public double getSalePrice() {
		return salePrice;
	}

	public int getID() {
		return PID;
	}

	public Product getDeposit() {
		return deposit;
	}

	public int getMultiplicator() {
		return multiplicator;
	}

	public String getName() {
		return name;
	}
	
	public void setMultiplicator(int multiplicator){
		this.multiplicator = multiplicator;
	}
}
